﻿using Jodell.Transport.Modbus;
using log4net;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Jodell.Lua
{
    public class Lua_ModbusRtuClient : IDisposable
    {
        ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        public Lua_ModbusRtuClient()
        {

        }
        private SerialPort mport;
        private readonly object sendLock = new object();
        ///<inheritdoc/>
        public bool IsConnected { get { return mport?.IsOpen ?? false; } }
        ///<inheritdoc/>
        public string PortName { get; set; }
        ///<inheritdoc/>
        public int BaudRate { get; set; } = 115200;
        ///<inheritdoc/>
        public Parity Parity { get; set; } = Parity.None;
        ///<inheritdoc/>
        public StopBits StopBits { get; set; } = StopBits.One;
        ///<inheritdoc/>
        public int WriteTimeout { get; set; } = 1000;
        ///<inheritdoc/>
        public int ReadTimeout { get; set; } = 1000;
        public IModbusResolver Resolver { get; set; }


        private void Init()
        {
            mport = new SerialPort();
            mport.BaudRate = this.BaudRate;
            mport.Parity = this.Parity;
            mport.StopBits = this.StopBits;
            mport.PortName = this.PortName;
            mport.Open();
            mport.WriteTimeout = this.WriteTimeout;
            mport.ReadTimeout = this.ReadTimeout;
            mport.BaseStream.ReadTimeout = this.ReadTimeout;
            mport.BaseStream.WriteTimeout = this.WriteTimeout;

            if (Resolver == null)
            {
                Resolver = new ModbusResolver();
            }
        }
        /// <summary>
        /// 数据发送
        /// </summary>
        /// <param name="datas"></param>
        /// <returns></returns>
        private ModbusRecInfo SendData(byte[] datas)
        {
            ModbusRecInfo info = null;
            try
            {
                lock (sendLock)
                {
                    mport.Write(datas, 0, datas.Length);

                    if (datas[0] == 0) //广播通讯 无响应
                    {
                        return null;
                    }

                    _log.Info(datas._ToString());
                    Stopwatch watch = new Stopwatch();
                    Resolver.Reset();
                    byte[] buffer = new byte[256];
                    watch.Start();
                    while (watch.ElapsedMilliseconds <= ReadTimeout)
                    {
                        if (mport.BytesToRead > 0)
                        {
                            var len = mport.Read(buffer, 0, 256);
                            StringBuilder sb = new StringBuilder();
                            for (int i = 0; i < len; i++)
                            {
                                sb.Append(buffer[i].ToString("X2"));
                                sb.Append(' ');
                            }
                            _log.Info(sb.ToString());
                            info = Resolver.AddRange(buffer, len);
                            if (info.Ret)
                                break;
                        }
                    }
                    if (watch.ElapsedMilliseconds > ReadTimeout)
                    {
                        throw new TimeoutException("接收超时");
                    }
                }
            }
            catch
            {
                throw;
            }
            return info;
        }


        /// <inheritdoc/>
        public int ReadHoldingRegisters(byte unitIdentifier, ushort startingAddress, ushort quantity, out byte[] datas)
        {
            int ret;
            datas = null;
            try
            {
                if (!this.IsConnected)
                {
                    return Lua_ErrorCode.Lua_Error_NotConnect;
                }
                List<byte> list = new List<byte>
                {
                    unitIdentifier,
                    (byte)ModbusFunctionCode.ReadHoldingRegisters
                };
                list.AddReverse(startingAddress);
                list.AddReverse(quantity);
                var crc = CrcUtil.Crc(list);
                list.Add(crc);

                datas = SendData(list.ToArray()).RecData.Take(quantity * 2).ToArray();
                ret = Lua_ErrorCode.Lua_OK;
            }
            catch
            {
                ret = Lua_ErrorCode.Lua_Error_TimeOut;
            }
            return ret;
        }

        /// <inheritdoc/>
        public int ReadInputRegisters(byte unitIdentifier, ushort startingAddress, ushort quantity, out byte[] datas)
        {
            int ret;
            datas = null;
            try
            {
                if (!this.IsConnected)
                {
                    return Lua_ErrorCode.Lua_Error_NotConnect;
                }
                List<byte> list = new List<byte>
                {
                    unitIdentifier,
                    (byte)ModbusFunctionCode.ReadInputRegisters
                };
                list.AddReverse(startingAddress);
                list.AddReverse(quantity);
                var crc = CrcUtil.Crc(list);
                list.Add(crc);

                datas = SendData(list.ToArray()).RecData.Take(quantity * 2).ToArray();
                ret = Lua_ErrorCode.Lua_OK;
            }
            catch
            {
                ret = Lua_ErrorCode.Lua_Error_TimeOut;
            }
            return ret;
        }
        /// <inheritdoc/>
        public int WriteMultipleRegisters(byte unitIdentifier, ushort startingAddress, NLua.LuaTable dataset)
        {
            int ret;
            try
            {

                if (!this.IsConnected)
                {
                    ret = Lua_ErrorCode.Lua_Error_NotConnect;
                }
                var quantity = (UInt16)(dataset.Values.Count / 2);
                List<byte> list = new List<byte>
                {
                    unitIdentifier,
                    (byte)ModbusFunctionCode.WriteMultipleRegisters
                };
                list.AddReverse(startingAddress);
                list.AddReverse(quantity);
                list.Add((byte)(quantity * 2));
                foreach (var d in dataset.Values)
                {
                    Int64 b = Convert.ToInt64(d);
                    byte c = (byte)b;
                    list.Add(c);
                }
                var crc = CrcUtil.Crc(list);
                list.Add(crc);
                SendData(list.ToArray());
                ret = Lua_ErrorCode.Lua_OK;
            }
            catch
            {
                ret = Lua_ErrorCode.Lua_Error_TimeOut;
            }
            return ret;
        }

        public int WriteMultipleRegisters(byte unitIdentifier, ushort startingAddress, byte[] dataset)
        {
            int ret;
            try
            {

                if (!this.IsConnected)
                {
                    ret = Lua_ErrorCode.Lua_Error_NotConnect;
                }
                var quantity = (UInt16)(dataset.Length / 2);
                List<byte> list = new List<byte>
                {
                    unitIdentifier,
                    (byte)ModbusFunctionCode.WriteMultipleRegisters
                };
                list.AddReverse(startingAddress);
                list.AddReverse(quantity);
                list.Add((byte)(quantity * 2));
                foreach (var d in dataset)
                {
                    list.Add(d);
                }
                var crc = CrcUtil.Crc(list);
                list.Add(crc);
                SendData(list.ToArray());
                ret = Lua_ErrorCode.Lua_OK;
            }
            catch
            {
                ret = Lua_ErrorCode.Lua_Error_TimeOut;
            }
            return ret;
        }
        public int ReadUInt16(byte unitIdentifier, ushort startingAddress, out UInt16 data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, 1, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                data = BitConverter.ToUInt16(buffer, 0);
            }
            return ret;
        }
        public int ReadInt16(byte unitIdentifier, ushort startingAddress, out Int16 data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, 1, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                data = BitConverter.ToInt16(buffer, 0);
            }
            return ret;
        }

        public int ReadUInt32(byte unitIdentifier, ushort startingAddress, out UInt32 data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, 2, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                data = BitConverter.ToUInt32(buffer, 0);
            }
            return ret;
        }
        public int ReadInt32(byte unitIdentifier, ushort startingAddress, out Int32 data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, 2, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                data = BitConverter.ToInt32(buffer, 0);
            }
            return ret;
        }

        public int ReadUInt64(byte unitIdentifier, ushort startingAddress, out UInt64 data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, 4, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                data = BitConverter.ToUInt64(buffer, 0);
            }
            return ret;
        }
        public int ReadInt64(byte unitIdentifier, ushort startingAddress, out Int64 data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, 4, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                data = BitConverter.ToInt64(buffer, 0);
            }
            return ret;
        }

        public int Read03FloatCDAB(byte unitIdentifier, ushort startingAddress, out float data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, 2, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                data = BitConverter.ToSingle(buffer, 0);
            }
            return ret;
        }
        public int Read03DoubleCDAB(byte unitIdentifier, ushort startingAddress, out double data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, 4, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                data = BitConverter.ToDouble(buffer, 0);
            }
            return ret;
        }


        public int ReadString(byte unitIdentifier, ushort startingAddress, ushort quantity, out string data)
        {
            data = string.Empty;
            byte[] buffer;
            var ret = ReadHoldingRegisters(unitIdentifier, startingAddress, quantity, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                for (int i = 0; i < buffer.Length / 2; i++)
                {
                    (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
                }
                //var len = Array.IndexOf(buffer, 0);
                data = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
            }
            return ret;
        }

        /// <inheritdoc/>
        private int Internal_WriteMultipleRegisters(byte unitIdentifier, ushort startingAddress, byte[] datas)
        {
            int ret;
            try
            {

                if (!this.IsConnected)
                {
                    ret = Lua_ErrorCode.Lua_Error_NotConnect;
                }
                var quantity = (UInt16)(datas.Length / 2);
                List<byte> list = new List<byte>
                {
                    unitIdentifier,
                    (byte)ModbusFunctionCode.WriteMultipleRegisters
                };
                list.AddReverse(startingAddress);
                list.AddReverse(quantity);
                list.Add((byte)(quantity * 2));
                list.AddRange(datas);

                var crc = CrcUtil.Crc(list);
                list.Add(crc);
                SendData(list.ToArray());
                ret = Lua_ErrorCode.Lua_OK;
            }
            catch
            {
                ret = Lua_ErrorCode.Lua_Error_TimeOut;
            }
            return ret;
        }

        public int WriteUInt16(byte unitIdentifier, ushort startingAddress, UInt16 data)
        {
            byte[] buffer = BitConverter.GetBytes(data);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }
        public int WriteInt16(byte unitIdentifier, ushort startingAddress, Int16 data)
        {
            byte[] buffer = BitConverter.GetBytes(data);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }
        public int WriteUInt32(byte unitIdentifier, ushort startingAddress, UInt32 data)
        {
            byte[] buffer = BitConverter.GetBytes(data);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }
        public int WriteInt32(byte unitIdentifier, ushort startingAddress, Int32 data)
        {
            byte[] buffer = BitConverter.GetBytes(data);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }

        public int WriteUInt64(byte unitIdentifier, ushort startingAddress, UInt64 data)
        {
            byte[] buffer = BitConverter.GetBytes(data);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }
        public int WriteInt64(byte unitIdentifier, ushort startingAddress, Int64 data)
        {
            byte[] buffer = BitConverter.GetBytes(data);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }

        public int WriteFloatCDAB(byte unitIdentifier, ushort startingAddress, double data)
        {
            byte[] buffer = BitConverter.GetBytes((float)data);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }
        public int WriteDoubleCDAB(byte unitIdentifier, ushort startingAddress, double data)
        {
            byte[] buffer = BitConverter.GetBytes((double)data);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }


        public int Read04FloatABCD(byte unitIdentifier, ushort startingAddress, out float data)
        {
            data = 0;
            byte[] buffer;
            var ret = ReadInputRegisters(unitIdentifier, startingAddress, 2, out buffer);
            if (ret == Lua_ErrorCode.Lua_OK)
            {
                Array.Reverse(buffer);
                data = BitConverter.ToSingle(buffer, 0);
            }
            return ret;
        }

        public int WriteString(byte unitIdentifier, ushort startingAddress, ushort quantity, string data)
        {
            byte[] tp = Encoding.ASCII.GetBytes(data);
            var buffer = new byte[quantity * 2];
            Array.Copy(tp, buffer, tp.Length);
            for (int i = 0; i < buffer.Length / 2; i++)
            {
                (buffer[i * 2 + 1], buffer[i * 2]) = (buffer[i * 2], buffer[i * 2 + 1]);
            }
            return this.Internal_WriteMultipleRegisters(unitIdentifier, startingAddress, buffer);
        }


        private bool WriteRegistAndWait(byte unitIdentifier, UInt16 regAddress)
        {
            this.WriteMultipleRegisters(unitIdentifier, regAddress, new byte[] { 0x00, 0x01 });
            Stopwatch sw = Stopwatch.StartNew();
            bool ret = false;
            var _timeout = this.ReadTimeout;
            this.ReadTimeout = 50;
            while (sw.ElapsedMilliseconds < 5000)
            {
                try
                {
                    var code = this.ReadHoldingRegisters(unitIdentifier, regAddress, 1, out byte[] rets);
                    if (code == 0)
                    {
                        if (rets[1] == 0x01)
                        {
                            ret = true;
                            break;
                        }
                    }
                }
                catch
                { }
            }
            this.ReadTimeout = _timeout;
            return ret;
        }

        public void WriteFileRecord(byte unitIdentifier, byte deviceNo, UInt16 recordId, UInt16 regLength, byte[] data)
        {
            if (!this.IsConnected)
            {
                throw new NotConnectedException();
            }
            List<byte> list = new List<byte>
            {
                unitIdentifier,
                (byte)ModbusFunctionCode.WriteFileRecord,
                0x00,
                deviceNo,
                0x00,
                0x00
            };
            list.AddReverse(recordId);      //byte6-7
            list.AddReverse(regLength);     //byte8-9

            for (int i = 0; i < regLength; i++)
            {
                list.Add(data[i * 2]);
                list.Add(data[i * 2 + 1]);
            }
            var crc = CrcUtil.Crc(list);
            list.Add(crc);
            SendData(list.ToArray());
        }

        public int WriteFile(byte unitIdentifier, int deviceNo, string path)
        {
            var stream = File.OpenRead(path);

            var deviceType1 = new byte[64];
            stream.Read(deviceType1, 0, deviceType1.Length);
            var code = this.ReadHoldingRegisters(unitIdentifier, 502 + 8, 32, out byte[] deviceType2);
            if (code != 0)
            {
                return code;
            }
            for (int i = 0; i < deviceType2.Length / 2; i++)
            {
                (deviceType2[i * 2 + 1], deviceType2[i * 2]) = (deviceType2[i * 2], deviceType2[i * 2 + 1]);
            }
            bool equal = true;
            for (int i = 0; i < 64; i++)
            {
                if (deviceType1[i] != deviceType2[i])
                {
                    equal = false;
                    break;
                }
            }
            if (!equal)
            {
                return Lua_ErrorCode.Lua_Error_DeviceTypeNotEqual;
            }
            stream.Seek(64 + 16, SeekOrigin.Begin);



            byte[] upgradeBuffer = new byte[128];   //发送缓冲区
            int dataLen = 0;                        //当前帧数据长度
            double totalSendCount;                  //待发送数据长度
            double sendedCount = 0;                 //已发送数据长度
            int recordId = 0;                       //发送帧ID
            bool ret = true;
            bool upgradeRet = false;                //升级结果
            bool changeStateFinised;                //切换状态结果


            totalSendCount = stream.Length - 64;

            //500 状态切换
            changeStateFinised = WriteRegistAndWait(unitIdentifier, 500);
            if (!changeStateFinised)
            {
                return Lua_ErrorCode.Lua_Error_Change500;
            }

            //升级 发送文件
            Stopwatch sw = Stopwatch.StartNew();
            sw.Reset();
            sw.Start();
            this.ReadTimeout = 1000;
            this.WriteTimeout = 1000;
            while (sw.ElapsedMilliseconds < 60_000)
            {
                if (ret)
                {
                    ret = false;
                    dataLen = stream.Read(upgradeBuffer, 0, upgradeBuffer.Length);
                    if (dataLen < 1)
                    {
                        upgradeRet = true;
                        break;
                    }
                }
                try
                {
                    this.WriteFileRecord(unitIdentifier, (byte)deviceNo, (UInt16)recordId, (UInt16)(dataLen / 2), upgradeBuffer);
                    sendedCount += dataLen;
                    ret = true;
                    recordId++;
                }
                catch { }
            }
            if (!upgradeRet)
            {
                return Lua_ErrorCode.Lua_Error_TimeOut;
            }

            //501 状态切换
            ret = WriteRegistAndWait(unitIdentifier, 501);
            if (!ret)
            {
                return Lua_ErrorCode.Lua_Error_Change501;
            }

            return Lua_ErrorCode.Lua_OK;
        }


        public void Connect()
        {
            Init();
        }
        public void Disconnect()
        {
            mport?.Close();
        }

        public void Dispose()
        {
            mport?.Close();
        }
    }
}
